#pragma once
#include "Runtime/BattleFirePrefix.h"

namespace Alice
{
	// special task priorities for tasks that are never sent to the scheduler
	enum class EExtendedTaskPriority
	{
		None,
		Inline, // a task priority for "inline" task execution - a task is executed "inline" by the thread that unlocked it, w/o scheduling
		TaskEvent, // a task priority used by task events, allows to shortcut task execution

#if TASKGRAPH_NEW_FRONTEND
		// for integration with named threads
		GameThreadNormalPri,
		GameThreadHiPri,
		GameThreadNormalPriLocalQueue,
		GameThreadHiPriLocalQueue,

		RenderThreadNormalPri,
		RenderThreadHiPri,
		RenderThreadNormalPriLocalQueue,
		RenderThreadHiPriLocalQueue,

		RHIThreadNormalPri,
		RHIThreadHiPri,
		RHIThreadNormalPriLocalQueue,
		RHIThreadHiPriLocalQueue,
#endif

		Count
	};
	
    enum class ETaskPriority : int8
	{
		High,
		Normal,
		Default = Normal,
		ForegroundCount,
		BackgroundHigh = ForegroundCount,
		BackgroundNormal,
		BackgroundLow,
		Count,
		Inherit, //Inherit the TaskPriority from the launching Task or the Default Priority if not launched from a Task.
	};

	inline const TCHAR* ToString(ETaskPriority Priority)
	{
		if (Priority < ETaskPriority::High || Priority >= ETaskPriority::Count)
		{
			return nullptr;
		}

		const TCHAR* TaskPriorityToStr[] =
		{
			TEXT("High"),
			TEXT("Normal"),
			TEXT("BackgroundHigh"),
			TEXT("BackgroundNormal"),
			TEXT("BackgroundLow")
		};
		return TaskPriorityToStr[(int32)Priority];
	}

	inline bool ToTaskPriority(const TCHAR* PriorityStr, ETaskPriority& OutPriority)
	{
		if (lstrcmpi(PriorityStr, ToString(ETaskPriority::High)) == 0)
		{
			OutPriority = ETaskPriority::High;
			return true;
		}

		if (lstrcmpi(PriorityStr, ToString(ETaskPriority::Normal)) == 0)
		{
			OutPriority = ETaskPriority::Normal;
			return true;
		}

		if (lstrcmpi(PriorityStr, ToString(ETaskPriority::BackgroundHigh)) == 0)
		{
			OutPriority = ETaskPriority::BackgroundHigh;
			return true;
		}

		if (lstrcmpi(PriorityStr, ToString(ETaskPriority::BackgroundNormal)) == 0)
		{
			OutPriority = ETaskPriority::BackgroundNormal;
			return true;
		}

		if (lstrcmpi(PriorityStr, ToString(ETaskPriority::BackgroundLow)) == 0)
		{
			OutPriority = ETaskPriority::BackgroundLow;
			return true;
		}

		return false;
	}
	enum class ETaskFlags
	{
		None,
		DoNotRunInsideBusyWait // do not pick this task for busy-waiting
	};
	
    template<typename ResultType>
    class TTask;
    class TaskBase;
    class TaskBase
    {
    public:
        class Prerequisites
        {
        public:
            void Push(TaskBase*inPrerequisite)
            {
                mMutex.lock();
                mPrerequisites.push_back(inPrerequisite);
                mMutex.unlock();
            }
            std::vector<TaskBase*> mPrerequisites;
            std::mutex mMutex;
        };
        class Subsequents
        {
        public:
            std::atomic<bool>  bIsClosed;
            std::vector<TaskBase*> mPrerequisites;
            std::mutex mMutex;
        };
        Prerequisites mPrerequisites;
        Subsequents mSubsequents;
    	// the number of times that the task should be unlocked before it can be scheduled or completed
    	// initial count is 1 for launching the task (it can't be scheduled before it's launched)
    	// reaches 0 the task is scheduled for execution.
    	// NumLocks's the most significant bit (see `ExecutionFlag`) is set on task execution start, and indicates that now 
    	// NumLocks is about how many times the task must be unlocked to be completed
    	static constexpr uint32 NumInitialLocks = 1;
    	std::atomic<uint32> NumLocks{ NumInitialLocks };
        virtual ~TaskBase()
        {
            
        }
        // The task will be executed only when all prerequisites are completed. The task type must be a task handle that holds a pointer to
        // TaskBase as its `Pimpl` member (see Tasks::TTaskBase).
        // Must not be called concurrently
        bool AddPrerequisites(TaskBase* inPrerequisite)
        {
            mPrerequisites.Push(inPrerequisite);
            return true;
        }
    	// The task will be executed only when all prerequisites are completed.
    	// Must not be called concurrently.
    	// @param InPrerequisites - an iterable collection of tasks
    	template<typename PrerequisiteCollectionType, decltype(std::declval<PrerequisiteCollectionType>().begin())* = nullptr>
		void AddPrerequisites(const PrerequisiteCollectionType& InPrerequisites)
        {
	        
        }
    	// Tries to schedule task execution. Returns false if the task has incomplete dependencies (prerequisites or is blocked by a pipe). 
    	// In this case the task will be automatically scheduled when all dependencies are completed.
    	bool TryLaunch(uint64 TaskSize)
        {
        	bool wakeupWorker=true;
        	return TryUnlock(wakeupWorker);
        }
    	// A task can be locked for execution (by prerequisites or if it's not launched yet) or for completion (by nested tasks).
    	// This method is called to unlock the task and so can result in its scheduling (and execution) or completion
    	bool TryUnlock(bool& bWakeUpWorker)
        {
        	return true;
        }
        virtual void ExecuteTask() = 0;
    };
    // an extension of TaskBase for tasks that return a result.
    // Stores task execution result and provides an access to it.
    template<typename ResultType>
    class TTaskWithResult : public TaskBase
    {
    public:
        TTypeCompatibleBytes<ResultType> mResult;
        TTaskWithResult(const TCHAR* inDebugName)
        {
            
        }
    };
    // Task implementation that can be executed, as it stores task body. Generic version (for tasks that return non-void results).
    // In most cases it should be allocated on the heap and used with TRefCountPtr, e.g. @see FTaskHandle. 
    template<typename TaskBodyType, typename ResultType = FunctionReturnType<TaskBodyType>, typename Enable = void>
    class TExecutableTaskBase : public TTaskWithResult<ResultType>
    {
    public:
        TTypeCompatibleBytes<TaskBodyType> mTaskBody;
        TExecutableTaskBase(const TCHAR* inDebugName,TaskBodyType&&inTaskBody)
            :TTaskWithResult<ResultType>(inDebugName)
        {
            new (&mTaskBody)TaskBodyType(std::move(inTaskBody));
        }
        virtual void ExecuteTask() override final
        {
            
        }
    };
    // a specialization for tasks that don't return results
    template<typename TaskBodyType>
    class TExecutableTaskBase<TaskBodyType, typename std::enable_if<std::is_same_v<FunctionReturnType<TaskBodyType>,void>>::type> : public TaskBase
    {
    public:
        TTypeCompatibleBytes<TaskBodyType> mTaskBody;
        TExecutableTaskBase(const TCHAR* inDebugName,TaskBodyType&&inTaskBody)
        {
            new (&mTaskBody)TaskBodyType(std::move(inTaskBody));
        }
        virtual void ExecuteTask() override final
        {
            TaskBodyType& taskBody = *mTaskBody.GetTypedPtr();
            taskBody();
        }
    };
    template<typename TaskBodyType>
    class TExecutableTask final : public TExecutableTaskBase<TaskBodyType>
    {
    public:
        TExecutableTask(const TCHAR* inDebugName, TaskBodyType&& inTaskBody)
            :TExecutableTaskBase<TaskBodyType>(inDebugName,std::forward<TaskBodyType>(inTaskBody))
        {
            
        }
        static TExecutableTask* Create(const TCHAR* inDebugName, TaskBodyType&& inTaskBody)
        {
            return new TExecutableTask(inDebugName,std::forward<TaskBodyType>(inTaskBody));
        }
    };
    // a specialization for tasks that don't return results
    /*template<typename TaskBodyType>
    class TExecutableTaskBase<TaskBodyType> : public TaskBase
    {
    public:
    };*/
    // a special kind of task that is used for signalling or dependency management. It can have prerequisites or be used as a prerequisite for other tasks. 
    // It's optimized for the fact that it doesn't have a task body and so doesn't need to be scheduled and executed
    class TaskEventBase : public TaskBase
    {
    public:
    };
    // a common part of the generic `TTask<ResultType>` and its `TTask<void>` specialisation
    class TaskHandle
    {
    public:
        TaskBase* mTaskBase;
        TaskHandle(TaskBase* inOther=nullptr):mTaskBase(inOther)
        {
            
        }
    	// launches a task for asynchronous execution
		// @param DebugName - a unique name for task identification in debugger and profiler, is compiled out in test/shipping builds
		// @param TaskBody - a functor that will be executed asynchronously
		// @param Priority - task priority that affects when the task will be executed
		// @param TaskFlags - task config options
		// @return a trivially relocatable instance that can be used to wait for task completion or to obtain task execution result
		template<typename TaskBodyType>
		void Launch(
			const TCHAR* inDebugName,
			TaskBodyType&& inTaskBody,
			ETaskPriority inPriority = ETaskPriority::Normal,
			EExtendedTaskPriority inExtendedPriority = EExtendedTaskPriority::None,
			ETaskFlags inFlags = ETaskFlags::None
		)
		{
			using FExecutableTask = TExecutableTask<std::decay_t<TaskBodyType>>;
			FExecutableTask* Task = FExecutableTask::Create(inDebugName, std::forward<TaskBodyType>(inTaskBody), inPriority, inExtendedPriority, inFlags);
			// this must happen before launching, to support an ability to access the task itself from inside it
			mTaskBase = Task;
			Task->TryLaunch(sizeof(*Task));
		}

		// launches a task for asynchronous execution, with prerequisites that must be completed before the task is scheduled
		// @param DebugName - a unique name for task identification in debugger and profiler, is compiled out in test/shipping builds
		// @param TaskBody - a functor that will be executed asynchronously
		// @param Prerequisites - tasks or task events that must be completed before the task being launched can be scheduled, accepts any 
		// iterable collection (.begin()/.end()), `Tasks::Prerequisites()` helper is recommended to create such collection on the fly
		// @param Priority - task priority that affects when the task will be executed
		// @param TaskFlags - task config options
		// @return a trivially relocatable instance that can be used to wait for task completion or to obtain task execution result
		template<typename TaskBodyType, typename PrerequisitesCollectionType>
		void Launch(
			const TCHAR* inDebugName,
			TaskBodyType&& inTaskBody,
			PrerequisitesCollectionType&& inPrerequisites,
			ETaskPriority inPriority = ETaskPriority::Normal,
			EExtendedTaskPriority inExtendedPriority = EExtendedTaskPriority::None,
			ETaskFlags inFlags = ETaskFlags::None
		)
		{
			//check(!IsValid());

			using FExecutableTask = TExecutableTask<std::decay_t<TaskBodyType>>;
			FExecutableTask* Task = FExecutableTask::Create(inDebugName, std::forward<TaskBodyType>(inTaskBody));//, inPriority, inExtendedPriority, inFlags);
			Task->AddPrerequisites(std::forward<PrerequisitesCollectionType>(inPrerequisites));
			// this must happen before launching, to support an ability to access the task itself from inside it
        	mTaskBase = Task;
			Task->TryLaunch(sizeof(*Task));
		}
        // waits for task's completion without timeout. Tries to retract the task and execute it in-place, if failed - blocks until the task 
        // is completed by another thread.
        // @return true if the task is completed
        bool Wait() const
        {
            // we keep the return type as boolean even if always returning true
            // as we could merge some versions of the Wait API back at some point.
            return true;
        }
    };
    using FTask=TaskHandle;
    // a movable/copyable handle of `Private::TTaskWithResult*` with the API adopted for public usage.
    // implements Pimpl idiom
    template<typename ResultType>
    class TTask : public TaskHandle
    {
    public:
        TTask() = default;

        // waits until the task is completed and returns task's result
       /*ResultType& GetResult()
        {
            check(IsValid());
            TaskHandle::Wait();
            return static_cast<TTaskWithResult<ResultType>*>(Pimpl.GetReference())->GetResult();
        }*/

        // private constructor, valid instances can be created only by launching (see friends)
        explicit TTask(TaskBase* inOther)
            : TaskHandle(inOther)
        {}
    };

    template<>
    class TTask<void> : public TaskHandle
    {
    public:
        TTask() = default;

        void GetResult()
        {
            check(IsValid()); // to be consistent with a generic `TTask<ResultType>::GetResult()`
            Wait();
        }
        // private constructor, valid instances can be created only by launching (see friends)
        explicit TTask(TaskBase* inOther)
            : TaskHandle(inOther)
        {}
    };
    // A synchronisation primitive, a recommended substitution of `FEvent` for signalling between tasks. If used as a task prerequisite or 
    // a nested task, it doesn't block a worker thread. Optionally can use "busy waiting" - executing tasks while waiting.
    class EventTask : public TaskHandle
    {
    public:
        // all prerequisites must be added before triggering the event
        template<typename PrerequisitesType>
        void AddPrerequisites(const PrerequisitesType& Prerequisites)
        {
            //mTaskBase->AddPrerequisites(Prerequisites);
        }
    };
	namespace Tasks
	{
	    // launches a task for asynchronous execution
		// @param DebugName - a unique name for task identification in debugger and profiler, is compiled out in test/shipping builds
		// @param TaskBody - a functor that will be executed asynchronously
		// @param Priority - task priority that affects when the task will be executed
		// @param TaskFlags - task config options
		// @return a trivially relocatable instance that can be used to wait for task completion or to obtain task execution result
		template<typename TaskBodyType>
		TTask<FunctionReturnType<TaskBodyType>>* Launch(
			const TCHAR* inDebugName,
			TaskBodyType&& inTaskBody,
			ETaskPriority inPriority = ETaskPriority::Normal,
			EExtendedTaskPriority inExtendedPriority = EExtendedTaskPriority::None,
			ETaskFlags inFlags = ETaskFlags::None
		)
		{
			using FResult = FunctionReturnType<TaskBodyType>;
			TTask<FResult>* task=new TTask<FResult>;
			task->Launch(inDebugName, std::forward<TaskBodyType>(inTaskBody), inPriority, inExtendedPriority, inFlags);
			return task;
		}

		// launches a task for asynchronous execution, with prerequisites that must be completed before the task is scheduled
		// @param DebugName - a unique name for task identification in debugger and profiler, is compiled out in test/shipping builds
		// @param TaskBody - a functor that will be executed asynchronously
		// @param Prerequisites - tasks or task events that must be completed before the task being launched can be scheduled, accepts any 
		// iterable collection (.begin()/.end()), `Tasks::Prerequisites()` helper is recommended to create such collection on the fly
		// @param Priority - task priority that affects when the task will be executed
		// @param TaskFlags - task config options
		// @return a trivially relocatable instance that can be used to wait for task completion or to obtain task execution result
		template<typename TaskBodyType, typename PrerequisitesCollectionType>
		TTask<FunctionReturnType<TaskBodyType>>* Launch(
			const TCHAR* inDebugName,
			TaskBodyType&& inTaskBody,
			PrerequisitesCollectionType&& inPrerequisites,
			ETaskPriority inPriority = ETaskPriority::Normal,
			EExtendedTaskPriority inExtendedPriority = EExtendedTaskPriority::None,
			ETaskFlags inFlags = ETaskFlags::None
		)
		{
			using FResult = FunctionReturnType<TaskBodyType>;
			TTask<FResult>*task=new TTask<FResult>;
			task->Launch(inDebugName, std::forward<TaskBodyType>(inTaskBody), std::forward<PrerequisitesCollectionType>(inPrerequisites), inPriority, inExtendedPriority, inFlags);
			return task;
		}
	}
}